#ifndef __TDP2_DDS_HPP__
#define __TDP2_DDS_HPP__

#include "tdp2.hpp"

namespace TDP
{
	namespace Rendering
	{
		namespace DDSFile
		{
			struct Format
			{
				const char *name;

				UInt32 rMask;
				UInt32 gMask;
				UInt32 bMask;
				UInt32 aMask;

				UInt32 numBytes;
				bool hasAlpha;

				static const Format A8_k;
				static const Format RGBA8_k;
				static const Format RGBA5551_k;
				static const Format RGBA4_k;
				static const Format RGB8_k;
				static const Format RGB565_k;
				static const Format *FormatList_k[];		// Terminates on NULL
			};

			namespace CodecID
			{
				static const UInt32 DXT1_k		= 0x31545844;
				static const UInt32 DXT3_k		= 0x33545844;
				static const UInt32 DXT5_k		= 0x35545844;
			};

			namespace SurfaceDescBits
			{
				static const UInt32 Caps_k			= 0x00000001;
				static const UInt32 Height_k		= 0x00000002;
				static const UInt32 Width_k			= 0x00000004;
				static const UInt32 Pitch_k			= 0x00000008;
				static const UInt32 PixelFormat_k	= 0x00001000;
				static const UInt32 MipMapCount_k	= 0x00020000;	// Remove?
				static const UInt32 LinearSize_k	= 0x00080000;
				static const UInt32 Depth_k			= 0x00800000;
			};

			namespace PixelFormatBits
			{
				static const UInt32 AlphaPixels_k	= 0x00000001;
				static const UInt32 FourCC_k		= 0x00000004;
				static const UInt32 RGB_k			= 0x00000040;
			};

			namespace CapsBits
			{
				static const UInt32 Complex_k				= 0x00000008;
				static const UInt32 Texture_k				= 0x00001000;
				static const UInt32 MipMap_k				= 0x00400000;
			};

			namespace Caps2Bits
			{
				static const UInt32 CubeMap_k			= 0x00000200;
				static const UInt32 CubeMapPX_k			= 0x00000400;
				static const UInt32 CubeMapNX_k			= 0x00000800;
				static const UInt32 CubeMapPY_k			= 0x00001000;
				static const UInt32 CubeMapNY_k			= 0x00002000;
				static const UInt32 CubeMapPZ_k			= 0x00004000;
				static const UInt32 CubeMapNZ_k			= 0x00008000;
				static const UInt32 Volume_k			= 0x00200000;
			};

			class Header
			{
			protected:
				UInt8 _bytes[128];
				UInt32 _codec;
				UInt32 _width;
				UInt32 _height;
				UInt32 _depth;
				UInt32 _mipMapLevels;
				UInt32 _surfaceDescFlags;
				UInt32 _pixelFormatFlags;
				UInt32 _capsFlags;
				UInt32 _caps2Flags;

			public:
				// Resets a DDS header to defaults
				void Reset();
				// Encodes a long value into the header byte stream
				void CodeLong(UInt32 offset, UInt32 value);
				// Decodes a long value from the header byte stream
				static UInt32 DecodeLong(const UInt8 *b);
				static UInt32 DecodeLong(const char *b);

				Header();
				void SetDimensions(UInt32 width, UInt32 height, UInt32 depth, UInt32 mipMapLevels);
				void SetSurfaceDescFlag(UInt32 flag);
				void SetPixelFormatFlag(UInt32 flag);
				void SetCapsFlag(UInt32 flag);
				void SetCaps2Flag(UInt32 flag);
				void SetCodec(UInt32 codec);
				const UInt8 *ConstructHeader(const Format *format);
				bool ParseHeader(const UInt8 *b, Format *fmt);
				UInt32 Codec() const;
				UInt32 Width() const;
				UInt32 Height() const;
				UInt32 Depth() const;
				UInt32 MipMapLevels() const;
				UInt32 SurfaceDescFlags() const;
				UInt32 PixelFormatFlags() const;
				UInt32 CapsFlags() const;
				UInt32 Caps2Flags() const;
			};
		};
	};
};

namespace TDP
{
	namespace Rendering
	{
		namespace DDSFile
		{
			inline void Header::Reset()
			{
				int i;
				for(i=0;i<124;i++)
					_bytes[i] = 0;

				_width = _height = _pixelFormatFlags = _caps2Flags = _codec = _depth = _mipMapLevels = 0;

				_surfaceDescFlags = SurfaceDescBits::Caps_k |
					SurfaceDescBits::Height_k |
					SurfaceDescBits::Width_k |
					SurfaceDescBits::PixelFormat_k |
					SurfaceDescBits::Pitch_k;

				_pixelFormatFlags = PixelFormatBits::RGB_k;

				_capsFlags = CapsBits::Texture_k;
			}

			// Encodes a long value into the header byte stream
			inline void Header::CodeLong(UInt32 offset, UInt32 value)
			{
				UInt8 *b = _bytes + offset;
				b[0] = static_cast<UInt8>(value);
				b[1] = static_cast<UInt8>(value >> 8);
				b[2] = static_cast<UInt8>(value >> 16);
				b[3] = static_cast<UInt8>(value >> 24);
			}

			// Decodes a long value from the header byte stream
			inline UInt32 Header::DecodeLong(const UInt8 *b)
			{
				return b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
			}

			inline unsigned int Header::DecodeLong(const char *b)
			{
				return DecodeLong(reinterpret_cast<const UInt8 *>(b));
			}

			inline Header::Header()
			{
				Reset();
			};

			inline void Header::SetDimensions(UInt32 width, UInt32 height, UInt32 depth, UInt32 mipMapLevels)
			{
				_width = width;
				_height = height;

				if(depth > 1)
				{
					_surfaceDescFlags |= SurfaceDescBits::Depth_k;
					_depth = depth;
				}
				if(mipMapLevels > 1)
				{
					_capsFlags |= (CapsBits::MipMap_k | CapsBits::Complex_k);
					_mipMapLevels = mipMapLevels;
				}
			}

			inline void Header::SetSurfaceDescFlag(UInt32 flag)
			{
				_surfaceDescFlags |= flag;
			}

			inline void Header::SetPixelFormatFlag(UInt32 flag)
			{
				_pixelFormatFlags |= flag;
			}

			inline void Header::SetCapsFlag(UInt32 flag)
			{
				_capsFlags |= flag;
			}

			inline void Header::SetCaps2Flag(UInt32 flag)
			{
				_caps2Flags |= flag;
			}

			inline void Header::SetCodec(UInt32 codec)
			{
				_codec = codec;
				_pixelFormatFlags &= ~PixelFormatBits::RGB_k;
				_pixelFormatFlags |= PixelFormatBits::FourCC_k;

				_surfaceDescFlags &= ~SurfaceDescBits::Pitch_k;
				_surfaceDescFlags |= SurfaceDescBits::LinearSize_k;
			}

			inline const UInt8 *Header::ConstructHeader(const Format *format)
			{
				if(format && format->hasAlpha)
					_pixelFormatFlags |= PixelFormatBits::AlphaPixels_k;

				// Code magic
				_bytes[0] = 'D'; _bytes[1] = 'D'; _bytes[2] = 'S'; _bytes[3] = ' ';
				
				CodeLong(4  , 124);	// Size of DDSD structure
				CodeLong(8  , _surfaceDescFlags);
				CodeLong(12 , _height);
				CodeLong(16 , _width);
				if(format)
				{
					// Write the image pitch
					CodeLong(20, ((_width * format->numBytes) + 3) & (~3));
				}
				else
				{
					// Write the compressed size of the main image (a.k.a. highest-resolution)
					switch(_codec)
					{
					case CodecID::DXT1_k:
						{
							UInt32 hBlocks = _width / 4;
							UInt32 wBlocks = _height / 4;
							if(hBlocks < 1) hBlocks = 1;
							if(wBlocks < 1) wBlocks = 1;
							CodeLong(20, hBlocks*wBlocks*8);
						}
						break;
					case CodecID::DXT3_k:
					case CodecID::DXT5_k:
						{
							UInt32 hBlocks = _width / 4;
							UInt32 wBlocks = _height / 4;
							if(hBlocks < 1) hBlocks = 1;
							if(wBlocks < 1) wBlocks = 1;
							CodeLong(20, hBlocks*wBlocks*16);
						}
						break;
					default:;
					};
				}
				CodeLong(24, _depth);
				CodeLong(28, _mipMapLevels);

				CodeLong(76 , 32);
				CodeLong(80, _pixelFormatFlags);
				CodeLong(84, _codec);
				if(format)
				{
					CodeLong(88 , format->numBytes * 8);
					CodeLong(92 , format->rMask);
					CodeLong(96 , format->gMask);
					CodeLong(100, format->bMask);
					CodeLong(104, format->aMask);
				}

				CodeLong(108, _capsFlags);
				CodeLong(112, _caps2Flags);

				return _bytes;
			}

			inline bool Header::ParseHeader(const UInt8 *b, Format *fmt)
			{
				if(b[0] != 'D' || b[1] != 'D' || b[2] != 'S' || b[3] != ' ' ||
					DecodeLong(b+4) != 124 ||
					DecodeLong(b+76) != 32)
					return false;

				_surfaceDescFlags = DecodeLong(b+8);
				_height = DecodeLong(b+12);
				_width = DecodeLong(b+16);

				_depth = DecodeLong(b+24);
				_mipMapLevels = DecodeLong(b+28);
				
				_pixelFormatFlags = DecodeLong(b+80);
				_codec = DecodeLong(b+84);

				if(_pixelFormatFlags & PixelFormatBits::RGB_k)
				{
					fmt->numBytes = DecodeLong(b+88) / 8;
					fmt->rMask = DecodeLong(b+92);
					fmt->gMask = DecodeLong(b+96);
					fmt->bMask = DecodeLong(b+100);
					fmt->aMask = DecodeLong(b+104);
					fmt->hasAlpha = (_pixelFormatFlags & PixelFormatBits::AlphaPixels_k);
				}

				_capsFlags = DecodeLong(b+108);
				_caps2Flags = DecodeLong(b+112);

				return true;
			}

			inline UInt32 Header::Codec() const
			{
				return _codec;
			}

			inline UInt32 Header::Width() const
			{
				return _width;
			}
			inline UInt32 Header::Height() const
			{
				return _height;
			}
			inline UInt32 Header::Depth() const
			{
				return _depth;
			}
			inline UInt32 Header::MipMapLevels() const
			{
				return _mipMapLevels;
			}
			inline UInt32 Header::SurfaceDescFlags() const
			{
				return _surfaceDescFlags;
			}
			inline UInt32 Header::PixelFormatFlags() const
			{
				return _pixelFormatFlags;
			}
			inline UInt32 Header::CapsFlags() const
			{
				return _capsFlags;
			}
			inline UInt32 Header::Caps2Flags() const
			{
				return _caps2Flags;
			}
		};
	};
};


#endif
